﻿using System;
using System.Reflection;
using ApplicationGenerator.BusinessModule.MetaElements;
using ApplicationGenerator.BusinessModule.Interfaces;

namespace ApplicationGenerator.BusinessModule.Elements
{
    /// <summary>
    /// Fasada dla modeli
    /// </summary>
    internal class ModelFacade : IModelFacade
    {
        /// <summary>
        /// Kolekcja metamodeli
        /// </summary>
        private IMetaModelCollection _metaModelCollection;

        /// <summary>
        /// Konstruktor
        /// </summary>
        internal ModelFacade()
        {
            MetaModelManager metaManager = MetaModelManager.Instance;
            _metaModelCollection = metaManager.MetaModelCollection;
        }

        /// <summary>
        /// Tworzy instancję modelu
        /// </summary>
        /// <param name="name">Nazwa modelu</param>
        /// <returns>Utworzona instancja modelu</returns>
        public IModel CreateModel(string name)
        {
            IMetaModel metaAdapter = _metaModelCollection[name];
            if (metaAdapter == null)
            {
                throw new ArgumentNullException("Brak elementu w kolekcji.");
            }

            Type modelType = metaAdapter.ModelType;

            object createdObject = null;
            try
            {
                createdObject = Activator.CreateInstance(modelType);
            }
            catch (Exception ex)
            {
                throw new Exception("Nie można utworzyć elementu. Powód: " + ex.Message);
            }

            return new Model(metaAdapter, createdObject);
        }

        /// <summary>
        /// Pobiera instancję modelu 
        /// </summary>
        /// <param name="name">Nazwa modelu</param>
        /// <param name="id">Id instancji modelu</param>
        /// <returns>Pobrana instancja modelu</returns>
        public IModel LoadModel(string name, int id)
        {
            IMetaModel metaAdapter = _metaModelCollection[name];
            if (metaAdapter == null)
            {
                throw new ArgumentNullException("Brak elementu w kolekcji.");
            }

            MethodInfo findMethod = GetMethodInBaseClass(metaAdapter, "Find");
            object elementInstance = null;
            try
            {
                elementInstance = findMethod.Invoke(null, new object[] { id });
            }
            catch (Exception ex)
            {
                throw new Exception("Nie można odczytać elementu. Powód: " + ex.InnerException != null ? ex.InnerException.Message : ex.Message);
            }

            return new Model(metaAdapter, elementInstance);
        }

        /// <summary>
        /// Zapisuje instancję modelu
        /// </summary>
        /// <param name="element">Instancja modelu</param>
        public void SaveModel(IModel element)
        {
            element.Save();
        }

        /// <summary>
        /// Usuwa instancję modelu
        /// </summary>
        /// <param name="element">Instancja modelu</param>
        public void DeleteModel(IModel element)
        {
            element.Delete();
        }

        /// <summary>
        /// Waliduje instancję modelu
        /// </summary>
        /// <param name="element">Instancja modelu</param>
        /// <returns>Czy model jest poprawny ?</returns>
        public bool ValidateModel(IModel element)
        {
            return true;
        }

        /// <summary>
        /// Rekurencyjnie wyszukuje metody w klasach bazowych
        /// </summary>
        /// <param name="metaAdapter">Metamodel</param>
        /// <param name="methodName">Nazwa metody</param>
        /// <returns>Metoda</returns>
        private MethodInfo GetMethodInBaseClass(IMetaModel metaAdapter, string methodName)
        {
            MethodInfo methodInfo = null;
            Type baseType = metaAdapter.ModelType;
            while (methodInfo == null)
            {
                baseType = baseType.BaseType;
                if (baseType == null)
                {
                    throw new Exception("Błędna hierarchia dziedziczenia dla modelu " + metaAdapter.ModelName);
                }

                methodInfo = baseType.GetMethod("Find");
            }

            return methodInfo;
        }
    }
}
